﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace DailyFx.Book.Repo
{
	using Common;
    

    public class CsvWriter : IDisposable
    {
		/// <summary>
		///		Contains the <see cref="T:TextWriter"/> pointing to file stream .
		/// </summary>
        private readonly TextWriter _writerImpl;

		/// <summary>
		///		Contains the delimiter character separating each field.
		/// </summary>
		private char _delimiter;

		/// <summary>
		///		Contains the quotation character wrapping every field.
		/// </summary>
		private char? _quote;

		/// <summary>
		///		Indicates if force to wrapp each field with quote .
		/// </summary>
		private bool _forceQuote;

		/// <summary>
		///		Contains the current record number have been written .
		/// </summary>
        private long _currentRecordNumber;

		/// <summary>
		///		the fields of current record beening built .
		/// </summary>
		private readonly StringBuilder _fields;
		
		/// <summary>
		///		the buffer contains pending records to commit .
		/// </summary>
		private readonly StringBuilder _buffer;

		/// <summary>
		///		Indicates if the class is disposed .
		/// </summary>
		private bool disposed;
    
    
		/// <summary>
		///		C'tor
		/// </summary>
        public CsvWriter ( TextWriter writer )
        {
            Check.NotNull(writer, "writer");

            this._writerImpl = writer;

			this._delimiter = CsvReader.DefaultDelimiter;
			this._quote = CsvReader.DefaultQuote;

            this._fields = new StringBuilder( 128 );
            this._buffer = new StringBuilder( 2048 );
            
        }

        /// <summary>
        ///		Gets the <see cref="System.Text.Encoding"/> being used when writing into text .
        /// </summary>
        public Encoding Encoding
        {
            get {
                return this._writerImpl.Encoding;
            }
        }

        /// <summary>
		///		Gets the number of records that have been written by this <c>CsvWriter</c>..
        /// </summary>
        public long CurrentRecordNumber
        {
            get {
                return this._currentRecordNumber;
            }
        }


        /// <summary>
        ///		Gets or sets the character used to separate fields .
        /// </summary>
        public char Delimiter
        {
            get
            {
                return this._delimiter;
            }
            set
            {
				if (value == this._quote) {
					throw new ArgumentException( "Fields; delimiter and quote cannot be the same..");
				}
                this._delimiter = value;
            }
        }

        /// <summary>
        ///		Gets or sets the character used to wrapping fields .
        /// </summary>
        public char? Quote
        {
            get
            {
                return this._quote;
            }
            set
            {
				if (value == this._quote) {
					throw new ArgumentException( "Fields; quote and delimiter cannot be the same..");
				}
				if (!value.HasValue && this._forceQuote) {
					throw new ArgumentException( "Fields' quote symbol cannot be null when toggle on ForceQuote.");
				}
				this._quote = value;
            }
        }

		 /// <summary>
        ///		Gets or sets a value indicating whether quotes should always be written.
        /// </summary>
        public bool ForceQuote
        {
            get
            {
                return this._forceQuote;
            }
            set
            {
				if (!this._quote.HasValue && value) {
					throw new ArgumentException( "Fields' quote symbol cannot be null when toggle on ForceQuote.");
				}
                this._forceQuote = value;
            }
        }

        /// <summary>
        ///		Gets or sets the <see cref="String"/> used to separate lines within the CSV.
        /// </summary>
        /// <remarks>
        /// By default, this property is set to <see cref="Environment.NewLine"/>.
        /// </remarks>
        public string NewLine
        {
            get {
                return this._writerImpl.NewLine;
            }
            set {
                this._writerImpl.NewLine = value;
            }
        }

        /// <summary>
        ///		Writes a record to this <c>CsvWriter</c>.
		///		All fields within <paramref name="row"/> are written in the order they appear.
        /// </summary>
        public void WriteRecord ( CsvRecord row )
        {
			Check.NotNull(row, "record");
            Debug.Assert(this._buffer.Length == 0, "Expecting buffer to be empty.");

            this.bufferRecord( row );
            this.commitRecords();
        }

        /// <summary>
        ///		Writes a record to this <c>CsvWriter</c>.
		///		Any <see langword="null"/> values will be written as empty strings.
        /// </summary>
        public void WriteRecord ( params string[] values )
        {
			Check.NotNull(values, "values");
            Debug.Assert(this._buffer.Length == 0, "Expecting buffer to be empty.");

            this.bufferRecord( values );
            this.commitRecords();
        }

        /// <summary>
        ///		Writes a record to this <c>CsvWriter</c>.
		///		Any <see langword="null"/> values will be written as empty strings.
        /// </summary>
        public void WriteRecord ( IEnumerable<string> values )
        {
			Check.NotNull(values, "values");
            Debug.Assert(this._buffer.Length == 0, "Expecting buffer to be empty.");

			this.bufferRecord(values);
            this.commitRecords();
        }

        /// <summary>
        ///		Writes <paramref name="length"/> records to this <c>CsvWriter</c>.
		///		When writing a lot of data, it is possible that better performance 
		///		can be achieved by using this method.
        /// </summary>
        public void WriteRecords ( CsvRecord[] rows, int offset, int length )
        {
			Check.NotNull(rows, "records");
			
			if (offset < 0 || offset >= rows.Length ){
				throw new ArgumentException( "Invalid offset.");	
			}
			if ( offset + length > rows.Length ) {
				throw new ArgumentException( "Invalid length.");	
			}

            Debug.Assert(this._buffer.Length == 0, "Expecting buffer to be empty.");


            for (var i = offset; i < offset + length; ++i)
            {
                var record = rows[i];
				
				if (offset < 0 || offset >= rows.Length)
				{
					throw new ArgumentException ("Records within the buffer cannot be null..");
				}
                this.bufferRecord( record );
            }

            // we only flush once, when all records have been written
            this.commitRecords ();
        }

		/// <summary>
		///		Synchronously push whatever's in the buffer to the text writer and reset the buffer
		/// </summary>
		private void commitRecords ()
		{
			this._writerImpl.Write( this._buffer.ToString() );
			this._buffer.Length = 0;
		}

		// write a record to the buffer builder
		private void bufferRecord ( IEnumerable<string> fields )
		{
			Debug.Assert(fields != null, "Expecting non-null fields.");

			var b1st = true;

			foreach (var f in fields)
			{
				if (!b1st) {
					this._buffer.Append(this._delimiter );
				}

				this.WriteField ( f );
				b1st = false;
			}

			this._buffer.Append(this.NewLine);
			++this._currentRecordNumber;
		}


		/// <summary>
		///		Writes field to the buffer, escaping embedded quotes, 
		///		and wrapping in quotes as necessary .
		/// </summary>
		private void WriteField ( string field )
		{
			var quoted = this._forceQuote;
			this._fields.Length = 0;

			if (!string.IsNullOrEmpty(field))
			{
				// delimit to preserve white-space at the beginning or end of the value
				if ( IsWhitespace(field[0]) || IsWhitespace(field[field.Length - 1]))
				{
					quoted = true;
				}

				for (var i = 0; i < field.Length; ++i)
				{
					this.WriteChar (this._fields, field[i], ref quoted );
				}
			}

			if ( quoted )
			{
				if ( !this._quote.HasValue ) {
					throw new System.InvalidOperationException
						(
							String.Format
							(
								"Field requires quoting in order, but no field's quote has been set. " 
								+ "The field is: {0}", this._fields.ToString()
							)
						);
				}

				this._buffer.Append(this._quote).Append(this._fields).Append(this._quote);
			}
			else
			{
				this._buffer.Append(this._fields);
			}
		}

		/// <summary>
        ///		Writes a character to the buffer being used to construct a value.
		///		By default, this method will ensure <paramref name="quote"/> is <see langword="true"/> 
		///		if <paramref name="ch"/> is <see cref="Delimiter"/>, <see cref="Quote"/>, 
		///		or an end of line character. If <paramref name="ch"/> is <see cref="Quote"/>, 
		///		it will repeat it so that it is escaped in the fields .
        /// </summary>
        protected virtual void WriteChar ( StringBuilder buffer, char ch, ref bool quote )
        {
            quote = quote || 
				ch == this._delimiter || ch == this._quote || 
				ch == CsvReader.CR || ch == CsvReader.LF;

            buffer.Append(ch);

            if (ch == this._quote) {
                // field quotes is repeated so it is escaped .
                buffer.Append(ch);
            }
        }


        /// <summary>
        ///		Flushes hot buffer into file stream .
        /// </summary>
        public void Flush ()
        {
            Debug.Assert(this._buffer.Length == 0, "Expecting buffer to be empty.");
            this._writerImpl.Flush();
        }

        /// <summary>
        ///		Closes this <c>CsvWriter</c>.
        /// </summary>
        public void Close()
        {
            this.Dispose();
        }

        /// <summary>
        /// Disposes of this <c>CsvWriter</c>.
        /// </summary>
        public void Dispose ()
        {
            if ( this.disposed )
            {
                return;
            }

            this.Dispose2 ( true );
            GC.SuppressFinalize(this);

        }

        /// <summary>
        ///		Disposes of this <c>CsvWriter</c>.
        /// </summary>
        protected virtual void Dispose2 ( bool disposing )
        {
            if ( disposing )
            {
				// this._writerImpl = null;
                // this._writerImpl.Dispose();
            }
			this.disposed = true;
        }

		

      
		/// <summary>
		/// 
		/// </summary>
        private  bool IsWhitespace( char ch )
        {
			return ch == CsvReader.SPACE || ch == CsvReader.TAB ;
        }


        

    }
}